package controllers;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import javax.swing.JPanel;

import classes.DDict;
import drawset.DrawSet;

public class MapController extends MyController {


	public MapController(JPanel P) {
		super(P);

		this.myState = ControlState.move_canvas;

		setDebug(true);

	}
	public enum ControlState {
		do_nothing,
		move_canvas,
		move_actor,
		select_tiles;
	}

	public enum SelectionMode {
		select_point,
		select_line,
		select_circle,
		select_rectangle,
		select_fill
	}

	public class Actor {
		public	String	text;
		public	double	X, Y, W, H;
		public	double	screenX()	{	return toScreenX(X);	}
		public	double	screenY()	{	return toScreenY(Y);	}
		public	double	screenW()	{	return W*zoom;			}
		public	double	screenH()	{	return H*zoom;			}

		public	void	draw(Graphics G) {
			G.drawRect((int) screenX(), (int) screenY(), (int) screenW(), (int) screenH());
			G.drawArc((int) screenX(), (int) screenY(), (int) screenW(), (int) screenH(), 0, 360);
		}
	}

	public Set<Point>				selected	= DrawSet.emptySet();
	public Set<Point>				filled		= DrawSet.emptySet();
	public HashMap<String, Actor>	actors		= new HashMap<String, Actor>();

	public ControlState				myState		= ControlState.select_tiles;
	public SelectionMode			selectMode	= SelectionMode.select_point;

	protected double	zoom		= 25D;
	protected double	origonX		= 0D;
	protected double	origonY		= 0D;

	public double	toCanvasX(double screenX)		{	return	(screenX-(panel.getWidth()/2D))/zoom - origonX;		}
	public double	toCanvasY(double screenY)		{	return	(screenY-(panel.getHeight()/2D))/zoom - origonY;	}
	public double	toScreenX(double canvasX)		{	return	(canvasX + origonX) * zoom + panel.getWidth()/2D;	}
	public double	toScreenY(double canvasY)		{	return	(canvasY + origonY) * zoom + panel.getHeight()/2D;	}

	public int		toTileX(int x)					{	return	(int) Math.floor(toCanvasX((double) x));		}
	public int		toTileY(int y)					{	return	(int) Math.floor(toCanvasY((double) y));		}

	public double	cameraX()						{	return	toCanvasX(0D);						}
	public double	cameraY()						{	return	toCanvasY(0D);						}
	public double	cameraW()						{	return	panel.getWidth() / zoom;			}
	public double	cameraH()						{	return	panel.getHeight() / zoom;			}

	protected DDict<Integer,Point2D>		mCanvasDown	= new DDict<Integer, Point2D>(new Point(0,0));
	protected DDict<Integer,Point2D>		mCanvasUp	= new DDict<Integer, Point2D>(new Point(0,0));
	protected Point2D						mCanvasPos	= new Point(0,0);

	protected DDict<Integer,Point2D>		mOrigonDown	= new DDict<Integer, Point2D>(new Point(0,0));
	protected DDict<Integer,Point2D>		mOrigonUp	= new DDict<Integer, Point2D>(new Point(0,0));
	protected Point2D						mOrigonPos	= new Point(0,0);

	//
	// Getters and setters
	//
	public double	getZoom() {
		return zoom;
	}
	public void setZoom(double value) {
		zoom = Math.max(1D, value);
		refresh();
	}
	public Point2D	getOrigon() {
		return new Point(
			(int) origonX, (int) origonY
		);
	}
	public void setOrigon(double x, double y) {
		origonX = x;
		origonY = y;
	}

	//
	// Internal methods
	//

	public Point2D	getDrag(int x, int y, int button) {
		return new Point(
			(int) (mScreenPos.getX() - mScreenDown.get(button).getX()),
			(int) (mScreenPos.getY() - mScreenDown.get(button).getY())
		);
	}

	public Rectangle	getTile(int x, int y) {
		return new Rectangle(
			(int) toScreenX(x),
			(int) toScreenY(y),
			(int) zoom,
			(int) zoom
		);
	}
	public Rectangle	getTile(Point2D P) 				{	return getTile((int) P.getX(), (int) P.getY());							}

	public Point 		toTile(int x, int y) 			{	return new Point( toTileX(x), toTileY(y) );								}
	public Point		toTile(Point2D P)				{	return toTile( (int) P.getX(), (int) P.getY());							}
	public Rectangle	screenTile(int x, int y)		{	return getTile(toTile(x,y));											}
	public Rectangle	screenTile(Point P)				{	return getTile(toTile(P.x, P.y));										}

	//
	// Canvas Painting Methods
	//
	public void drawTile(Graphics G, int x, int y) {
		G.drawRect((int) toScreenX(x), (int) toScreenY(y), (int) zoom, (int) zoom);
	}
	public void drawActor(Graphics G, int x, int y) {
		G.drawArc((int) toScreenX(x), (int) toScreenY(y), (int) zoom, (int) zoom, 0, 360);
	}
	public void drawTiles(Graphics G, Set<Point> S) {
		Iterator<Point> itr = S.iterator();
		Point			P;

		while(itr.hasNext()) {
			P = itr.next();
			drawTile(G, P.x, P.y);
		}
	}
	public void drawActors(Graphics G, Set<Point> S) {
		Iterator<Point> itr = S.iterator();
		Point			P;

		while(itr.hasNext()) {
			P = itr.next();
			drawActor(G, P.x, P.y);
		}
	}

	public void fillTile(Graphics G, int x, int y) {
		G.fillRect((int) toScreenX(x), (int) toScreenY(y), (int) zoom, (int) zoom);
	}
	public void fillActor(Graphics G, int x, int y) {
		G.fillArc((int) toScreenX(x), (int) toScreenY(y), (int) zoom, (int) zoom, 0, 360);
	}
	public void fillTiles(Graphics G, Set<Point> S) {
		Iterator<Point> itr = S.iterator();
		Point			P;

		while(itr.hasNext()) {
			P = itr.next();
			fillTile(G, P.x, P.y);
		}
	}
	public void fillActors(Graphics G, Set<Point> S) {
		Iterator<Point> itr = S.iterator();
		Point			P;

		while(itr.hasNext()) {
			P = itr.next();
			fillActor(G, P.x, P.y);
		}
	}

	public void drawColumn(Graphics G, int x) {
		G.drawRect((int) toScreenX(x), 0, (int) toScreenX(x), panel.getHeight());
		G.drawRect((int) toScreenX(x+zoom), 0, (int) toScreenX(x+zoom), panel.getHeight());
	}
	public void drawRow(Graphics G, int y) {
		G.drawRect(0, (int) toScreenY(y), panel.getWidth(), (int) toScreenY(y));
		G.drawRect(0, (int) toScreenY(y+zoom), panel.getWidth(), (int) toScreenY(y+zoom));
	}

	//
	// Screen Drawing methods
	//

	public void DrawGrid(Graphics G) {
		int	cx = (int) Math.floor(cameraX());
		int	cy = (int) Math.floor(cameraY());
		int	x, y;

		for(x = 0; x < cameraW()+1; x++) {
			G.drawLine((int) toScreenX(cx+x), 0, (int) toScreenX(cx+x), panel.getHeight());
		}

		for(y = 0; y < cameraH()+1; y++) {
			G.drawLine(0, (int) toScreenY(cy+y), panel.getWidth(), (int) toScreenY(cy+y));
		}
	}

	public void DrawSelected(Graphics G) {
		G.setXORMode(panel.getBackground());
		fillTiles(G,selected);
		G.setPaintMode();
	}

	@Override
	public void paint(Graphics G) {
		DrawGrid(G);
		DrawSelected(G);
	}

	//
	// Map interface methods
	//

	@Override
	public void mousePressed(MouseEvent e) {
		super.mousePressed(e);
		mCanvasPos = this.toTile((int) mScreenPos.getX(), (int) mScreenPos.getY());
		mCanvasDown.put(e.getButton(), mCanvasPos);
		mOrigonDown.put(e.getButton(), getOrigon());

		if (e.getButton() == MouseEvent.BUTTON3) {
			int i = selectMode.ordinal();
			i = (i + 1) % SelectionMode.values().length;
			selectMode = SelectionMode.values()[i];
			refresh();
		} else if (e.getButton() == MouseEvent.BUTTON2) {
			if (myState == ControlState.move_canvas) {
				myState = ControlState.select_tiles;
			} else if (myState == ControlState.select_tiles) {
				myState = ControlState.move_canvas;
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		super.mouseMoved(e);
		mCanvasPos = this.toTile((int) mScreenPos.getX(), (int) mScreenPos.getY());
		mCanvasUp.put(e.getButton(), mCanvasPos);
		mOrigonUp.put(e.getButton(), getOrigon());
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		super.mouseWheelMoved(e);
		mCanvasPos = this.toTile((int) mScreenPos.getX(), (int) mScreenPos.getY());

		if (e.getWheelRotation() < 0d) {
			this.setZoom(getZoom() * 1.1);
		} else {
			this.setZoom(getZoom() / 1.1);
		}
		refresh();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		super.mouseMoved(e);
		mCanvasPos = this.toTile((int) mScreenPos.getX(), (int) mScreenPos.getY());
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		super.mouseDragged(e);
		mCanvasPos = this.toTile((int) mScreenPos.getX(), (int) mScreenPos.getY());

		Point2D	sDown	= mScreenDown.get(MouseEvent.BUTTON1);
		Point2D sPos	= mScreenPos;

		int	cxDown		= toTileX((int) sDown.getX());
		int	cyDown		= toTileY((int) sDown.getY());
		int cxPos		= toTileX((int) sPos.getX());
		int	cyPos		= toTileY((int) sPos.getY());

		if (myState == ControlState.select_tiles) {
			if (selectMode == SelectionMode.select_point) {
				selected		= DrawSet.getPoint(cxPos, cyPos);
				refresh();
			} else if (selectMode == SelectionMode.select_line) {
				selected		= DrawSet.getLine(cxDown, cyDown, cxPos, cyPos);
				refresh();
			} else if (selectMode == SelectionMode.select_circle) {
				selected		= DrawSet.getCircle(cxDown, cyDown, (int) Point2D.distance(cxDown, cyDown, cxPos, cyPos));
				refresh();
			} else if (selectMode == SelectionMode.select_rectangle) {
				selected		= DrawSet.getPointRect(cxDown, cyDown, cxPos, cyPos);
				refresh();
			}
		} else if (myState == ControlState.move_canvas) {
			Point2D oStart		= mOrigonDown.get(MouseEvent.BUTTON1);

			double	dx = (mScreenPos.getX() - mScreenDown.get(MouseEvent.BUTTON1).getX()) / zoom;
			double	dy = (mScreenPos.getY() - mScreenDown.get(MouseEvent.BUTTON1).getY()) / zoom;

			setOrigon(
				(oStart.getX() + dx),
				(oStart.getY() + dy)
			);
			refresh();
		}

	}


}
